/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/dserver.c 
 * @date        2010-04-05
 * @brief	server_framework
 * @see		dserver         
 * @history
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/resource.h>

#include <dlib/mempool.h>

#include "dserver.h"
#include "dserver_log.h"
#include "dserver_errno.h"

#include "config.h"
#include "protocol.h"
#include "filter.h"
#include "pipeline.h"
#include "reactor.h"

/** static env settings */
static dserver_setting_t settings;
/** global dserver context structure */
dserver_t dc;

/** for dump_and_restore */
static char exec_path[PATH_MAX];
static char **exec_argvs;

static void _show_usage(void);
static void _exec_argvs_exit(int argc);
static void _exec_argvs_init(int argc, const char **argv);
static void _create_pid_file(const char *pid_file);

/**
 * _setup_max_fd
 * @brief setup max fd defined in config-file
 * @param fd: fd number
 */
static void _setup_max_fd(uint32_t fd);

/**
 * _do_core_dump
 * @brief core dump
 * @param signo: received signal number
 */
static int _do_core_dump(int signo);

/**
 * _do_reload
 * @brief reload server with same env
 */
static void _do_reload(void);

/**
 * _dump_and_reload
 * @brief core dump and then reload
 * @param signo: received signal number
 */
static void _dump_and_reload(int signo);

/**
 * _signal_init
 * @brief setup signal handlers
 */
static int _signal_init(void);

int main(int argc, char *argv[]);

static inline void _show_usage(void)
{
	fprintf(stdout, "%s version %s\n"
			"Usage: %s [options]\n"
			"    -f  Order the conf-file of main-process.\n"
			"    -h  Show usage.\n",
			DSERVER_NAME, DSERVER_VERSION, DSERVER_NAME);
	return;
}

static inline void _exec_argvs_init(int argc, const char **argv)
{
	int i;
	int argv_len;

	realpath(argv[0], exec_path);
	exec_argvs = dmalloc(sizeof (char *) * (argc + 1));
	for (i = 0; i < argc; i++) {
		argv_len = strlen(argv[i]);
		exec_argvs[i] = dmalloc(argv_len + 1);
		strncpy(exec_argvs[i], argv[i], argv_len);
	}
	exec_argvs[i] = NULL;

	return;
}

static inline void _exec_argvs_exit(int argc)
{
	int i;

	for (i = 0; i < argc; i++)
		dfree(exec_argvs[i]);
	dfree(exec_argvs);

	return;
}

static inline void _create_pid_file(const char *pid_file)
{
	char pid[8];
	int fd = open(pid_file, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
	if (fd < 0) {
		DLOG_ERR(LOG_MAIN, "open file[%s] error: %s.", pid_file, strerror(errno));
		return;
	}
	snprintf(pid, 8, "%d\n", getpid());
	write(fd, pid, strlen(pid));
	fsync(fd);
	close(fd);
	return;
}

static inline void _setup_max_fd(uint32_t fd)
{
	struct rlimit rlim;

	if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
		DLOG_WARNING(LOG_MAIN, "getrlimit error, max_fd takes no changes.");
		return;
	}

	if (rlim.rlim_cur < fd)
		rlim.rlim_cur = fd;
	if (rlim.rlim_max < rlim.rlim_cur)
		rlim.rlim_max = rlim.rlim_cur;
	if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
		DLOG_WARNING(LOG_MAIN, "setrlimit error for max_fd, need root priv.\n");
		return;
	}

	return;
}

static int _do_core_dump(int signo)
{
	pid_t pid;
	struct rlimit rlim;
	struct rlimit rlim_new;
	struct sigaction sa;

	if ((pid = fork()) < 0)
		return -E_PROC_FORK;
	if (pid == 0) {
		if (getrlimit(RLIMIT_CORE, &rlim) == 0) {
			rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
			if (setrlimit(RLIMIT_CORE, &rlim_new) != 0) {
				rlim_new.rlim_cur = rlim.rlim_max;
				rlim_new.rlim_max = rlim.rlim_max;
				(void)setrlimit(RLIMIT_CORE, &rlim_new);
			}
		}
		if ((getrlimit(RLIMIT_CORE, &rlim) != 0) || rlim.rlim_cur == 0) {
			DLOG_ERR(LOG_MAIN, "failed to ensure corefile creation\n");
			return -E_UNEXPECTED;
		}

		sa.sa_handler = SIG_DFL;
		sa.sa_flags = 0;
		if (sigfillset(&sa.sa_mask) == -1) {
			DLOG_ERR(LOG_MAIN, "failed to sigfillset coredump signals.");
			return -E_UNEXPECTED;
		}
		if (sigaction(signo, &sa, NULL) == -1) {
			DLOG_ERR(LOG_MAIN, "sigerror, failed to setup coredumps.");
			return -E_UNEXPECTED;
		}
		raise(signo);
	}

	return pid;
}

static void _do_reload(void)
{
	DLOG_INFO(LOG_MAIN, "dserver daemon force to exit by signal.");

	reactor_exit(&dc);
	pipeline_exit(&dc);
	filter_exit(&dc);
	protocol_exit(&dc);

	dserver_log_exit();
	main_conf_exit(&settings);

#ifndef _DEBUG
	execvp(exec_path, exec_argvs);
#endif
	_exit(0);
}

static void _dump_and_reload(int signo)
{
	uint32_t i;

	DLOG_INFO(LOG_MAIN, "dserver catch signal[%d], coredump and reload program.",
		  signo);

	for (i = 3; i < settings.max_fds; i++)
		close(i);
	if (_do_core_dump(signo) <= 0)
		return;
	_do_reload();
	return;
}

static int _core_dump_signals[] = {
	SIGABRT, SIGFPE, SIGILL, SIGQUIT, SIGSEGV,
	SIGTRAP, SIGSYS, SIGBUS, SIGXCPU, SIGXFSZ,
#ifdef SIGEMT
	SIGEMT,
#endif
};

static int _signal_init(void)
{
	uint32_t i;
	struct sigaction sa;

	DLOG_DEBUG(LOG_MAIN, "dserver enters in signal_init function.");

	/* ignore SIGPIPE */
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	if (sigemptyset(&sa.sa_mask) == -1 ||
			sigaction(SIGPIPE, &sa, NULL) == -1) {
		DLOG_ERR(LOG_MAIN, "sigerror, failed to ignore SIGPIPE.");
		return -E_UNEXPECTED;
	}
	/* dump and reload program at coredump SIGNALs */
	sa.sa_handler = _dump_and_reload;
	sa.sa_flags = 0;
	if (sigfillset(&sa.sa_mask) == -1) {
		DLOG_ERR(LOG_MAIN, "sigerror, failed to sigfillset coredump signals.");
		return -E_UNEXPECTED;
	}
	for (i = 0; i < ARRAY_SIZE(_core_dump_signals); i++) {
		if (sigaction(_core_dump_signals[i], &sa, NULL) == -1) {
			DLOG_ERR(LOG_MAIN, "sigerror, failed to setup coredumps.");
			return -E_UNEXPECTED;
		}
	}

	pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, NULL);

	return RET_SUCCESS;
}

int main(int argc, char *argv[])
{
	int ret = RET_SUCCESS;
	int opt;

	while ((opt = getopt(argc, argv, "f:h")) != -1) {
		switch (opt) {
		case 'f':
			set_main_conf_file(optarg, &settings);
			break;
		case 'h':
		default:
			_show_usage();
			return 0;
		}
	}

	/** init the settings from conf files */
	if ((ret = main_conf_init(&settings)) < 0) {
		main_conf_exit(&settings);
		return ret;
	}
#ifdef _DEBUG
	dlib_confl_dump(settings.dserver_confl);
#endif

	dc.settings = &settings;

	_exec_argvs_init(argc, (const char **)argv);

	/** init log */
	if (dserver_log_init(settings.log_path) < 0) {
		_exec_argvs_exit(argc);
		main_conf_exit(&settings);
		return ret;
	}

	DLOG_INFO(LOG_MAIN, "dserver daemon starts ...");

	/** init envornment according to settings */
	_create_pid_file(settings.pid_file);
	_setup_max_fd(settings.max_fds);
	if ((ret = _signal_init()) < 0)
		goto log_out;

	if ((ret = daemon(1, 1)) < 0) {
		DLOG_ERR(LOG_MAIN, "daemonize error, dserver exits.");
		goto log_out;
	}

	/** init protocol, load all the protocol-modules */
	if ((ret = protocol_init(&dc)) < 0) {
		DLOG_ERR(LOG_MAIN, "protocol init error, dserver exits.");
		goto protocol_out;
	}

	/** init filter, load all the filter-modules */
	if ((ret = filter_init(&dc)) < 0) {
		DLOG_ERR(LOG_MAIN, "filter init error, dserver exits.");
		goto filter_out;
	}

	/** init pipeline, create pipelines with these filters */
	if ((ret = pipeline_init(&dc)) < 0) {
		DLOG_ERR(LOG_MAIN, "pipeline init error, dserver exits.");
		goto pipeline_out;
	}

	/** init reactor, create connection-pool and main events by libevent */
	dlib_lock_init(&dc.dserver_lock);
	if ((ret = reactor_init(&dc)) < 0) {
		DLOG_ERR(LOG_MAIN, "reactor init error, dserver exits.");
		goto reactor_out;
	}

	/** run dserver forever */
	DLOG_INFO(LOG_MAIN, "dserver running protocols forever...");
	if ((ret = reactor_run(&dc)) < 0) {
		DLOG_ERR(LOG_MAIN, "reactor runtime error, dserver exits.");
		goto reactor_out;
	}

	/** never be here */
	DLOG_ERR(LOG_MAIN, "Error, dserver daemon unexpected exit.");

reactor_out:
	dlib_lock_exit(&dc.dserver_lock);
	reactor_exit(&dc);

pipeline_out:
	pipeline_exit(&dc);

filter_out:
	filter_exit(&dc);

protocol_out:
	protocol_exit(&dc);

log_out:
	dserver_log_exit();
	_exec_argvs_exit(argc);
	main_conf_exit(&settings);

	return ret;
}

